// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.linqingying.gitcode.ui.component

import com.intellij.collaboration.ui.HtmlEditorPaneUtil
import com.intellij.collaboration.ui.html.AsyncHtmlImageLoader
import com.intellij.collaboration.ui.setHtmlBody
import com.intellij.collaboration.ui.util.name
import com.intellij.ide.ui.AntialiasingType
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.text.HtmlBuilder
import com.intellij.openapi.util.text.HtmlChunk
import com.intellij.ui.BrowserHyperlinkListener
import com.intellij.ui.HyperlinkAdapter
import com.intellij.util.ui.*
import com.linqingying.gitcode.exceptions.GitCodeStatusCodeException
import com.linqingying.gitcode.i18n.GitCodeBundle
import org.jetbrains.annotations.Nls
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import java.awt.event.KeyEvent
import java.net.URL
import javax.swing.*
import javax.swing.event.HyperlinkEvent
import javax.swing.text.DefaultCaret
import javax.swing.text.html.HTMLDocument
import javax.swing.text.html.StyleSheet

object GCHtmlErrorPanel {

  private const val ERROR_ACTION_HREF = "ERROR_ACTION"

  fun create(
    errorPrefix: String, error: Throwable,
    errorAction: Action? = null,
    horizontalAlignment: Int = SwingConstants.CENTER,
  ): JComponent {
    val model = GCImmutableErrorPanelModel(errorPrefix, error, errorAction)
    return create(model, horizontalAlignment)
  }

  fun create(model: GCErrorPanelModel, horizontalAlignment: Int = SwingConstants.CENTER): JComponent {

    val pane = SimpleHtmlPane(addBrowserListener = false).apply {
      foreground = NamedColorUtil.getErrorForeground()
      isFocusable = true

      addHyperlinkListener(object : HyperlinkAdapter() {
        override fun hyperlinkActivated(e: HyperlinkEvent) {
          if (e.description == ERROR_ACTION_HREF) {
            model.errorAction?.actionPerformed(ActionEvent(this@apply, ActionEvent.ACTION_PERFORMED, "perform"))
          }
        }
      })
      registerKeyboardAction(ActionListener {
        model.errorAction?.actionPerformed(it)
      }, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_FOCUSED)
    }

    Controller(model, pane, horizontalAlignment)
    return pane
  }

  private class Controller(
    private val model: GCErrorPanelModel,
    private val pane: JEditorPane,
    horizontalAlignment: Int,
  ) {

    private val alignmentText = when (horizontalAlignment) {
      SwingConstants.LEFT -> "left"
      SwingConstants.RIGHT -> "right"
      else -> "center"
    }

    init {
      model.addAndInvokeChangeEventListener(::update)
    }

    private fun update() {
      val error = model.error
      if (error != null) {
        pane.isVisible = true
        val errorTextBuilder = HtmlBuilder()
          .appendP(model.errorPrefix)
          .appendP(getLoadingErrorText(error))
        val errorAction = model.errorAction
        if (errorAction != null) {
          errorTextBuilder.br()
            .appendP(HtmlChunk.link(ERROR_ACTION_HREF, errorAction.name.orEmpty()))
        }
        pane.setHtmlBody(errorTextBuilder.toString())
      }
      else {
        pane.isVisible = false
        pane.setHtmlBody("")
      }
      // JDK bug - need to force height recalculation (see JBR-2256)
      pane.setSize(Int.MAX_VALUE / 2, Int.MAX_VALUE / 2)
    }

    private fun HtmlBuilder.appendP(chunk: HtmlChunk): HtmlBuilder = append(HtmlChunk.p().attr("align", alignmentText).child(chunk))

    private fun HtmlBuilder.appendP(@Nls text: String): HtmlBuilder = appendP(HtmlChunk.text(text))
  }

  @Nls
  fun getLoadingErrorText(error: Throwable): String {
    if (error is GitCodeStatusCodeException && error.error != null && error.error!!.message != null) {
      val gitcodeError = error.error!!
      val message = gitcodeError.message!!.removePrefix("[").removeSuffix("]")
      val builder = HtmlBuilder().append(message)
      if (message.startsWith("Could not resolve to a Repository", true)) {
        @NlsSafe
        val explanation = " Either repository doesn't exist or you don't have access. The most probable cause is that OAuth App access restrictions are enabled in organization."
        builder.append(explanation)
      }

      val errors = gitcodeError.errors?.map { e ->
        HtmlChunk.text(e.message ?: GitCodeBundle.message("gql.error.in.field", e.code, e.resource, e.field.orEmpty()))
      }
      if (!errors.isNullOrEmpty()) builder.append(": ").append(HtmlChunk.br()).appendWithSeparators(HtmlChunk.br(), errors)
      return builder.toString()
    }

    return error.message ?: GitCodeBundle.message("unknown.loading.error")
  }
}
@Suppress("FunctionName")
fun SimpleHtmlPane(
  additionalStyleSheet: StyleSheet? = null,
  addBrowserListener: Boolean = true,
  customImageLoader: AsyncHtmlImageLoader? = null,
  baseUrl: URL? = null,
): JEditorPane =
  JTextPane().apply {
    editorKit = HTMLEditorKitBuilder().withViewFactoryExtensions(
      ExtendableHTMLViewFactory.Extensions.WORD_WRAP,
      HtmlEditorPaneUtil.CONTENT_TOOLTIP,
      HtmlEditorPaneUtil.INLINE_ICON_EXTENSION,
      HtmlEditorPaneUtil.IMAGES_EXTENSION
    ).apply {
      if (additionalStyleSheet != null) {
        val defaultStyleSheet = StyleSheetUtil.getDefaultStyleSheet()
        additionalStyleSheet.addStyleSheet(defaultStyleSheet)
        withStyleSheet(additionalStyleSheet)
      }
    }.build()

    isEditable = false
    isOpaque = false
    if (addBrowserListener) {
      addHyperlinkListener(BrowserHyperlinkListener.INSTANCE)
    }
    margin = JBInsets(0, 0, 0, 0)
    GraphicsUtil.setAntialiasingType(this, AntialiasingType.getAAHintForSwingComponent())

    (caret as DefaultCaret).updatePolicy = DefaultCaret.NEVER_UPDATE

    if (customImageLoader != null) {
      document.putProperty(AsyncHtmlImageLoader.KEY, customImageLoader)
    }
    if (baseUrl != null) {
      (document as HTMLDocument).base = baseUrl
    }

    name = "Simple HTML Pane"
  }